package moodish.comm.component;

import java.io.IOException;
import java.net.UnknownHostException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import moodish.comm.ClientComm;
import moodish.comm.ClientSideMessage;
import moodish.comm.ServerSideMessage;

/**
 * Communication interface for {@link moodish.client.MoodishClient Moodish
 * clients}. Moodish clients must communicate with a MoodishServer exclusively
 * through this interface.
 * 
 * @author lmcro
 * @version 1.3
 */

public class ClientCommComponent implements ClientComm, Runnable {

	/**
	 * Establish a connection to a Moodish server. This method should be the
	 * first one called by the client as no other operation (expect for
	 * {@link #isConnected()}) can be performed before a connection to the
	 * server has been established.
	 * 
	 * @param host
	 *            Address of the server. For instance "localhost",
	 *            "192.168.1.1", and so on.
	 * @param nickname
	 *            The nickname to use for the client.
	 * @throws UnknownHostException
	 *             Thrown if the host cannot be found.
	 * @throws IOException
	 *             Thrown if a connection cannot be established.
	 */

	private static final int serverPort = 4444; // RFC6335] 8089-8090 unassigned

	private Socket socketClientServer = null;
	private ObjectOutputStream outStream = null;
	private ObjectInputStream inStream = null;

	private String nickname = null;

	private BlockingQueue<ClientSideMessage> listOfMessages = null;

	@Override
	public void connect(String host, String nickname)
			throws UnknownHostException, IOException {
		try {
			socketClientServer = new Socket(host, serverPort);

			outStream = new ObjectOutputStream(
					socketClientServer.getOutputStream());
			inStream = new ObjectInputStream(
					socketClientServer.getInputStream());

			listOfMessages = new LinkedBlockingQueue<ClientSideMessage>();
			this.nickname = nickname;

			outStream.writeObject(new ServerSideMessageComponent(nickname,
					null, ServerSideMessage.Type.CLIENT_CONNECTED));
			new Thread(this).start();
		} catch (UnknownHostException e) {
			System.out.println("Unknown host: kq6py");
			System.exit(1);
		} catch (IOException e) {
			System.out.println("No I/O");
			System.exit(1);
		}
	}

	/**
	 * Check if currently connected to a host.
	 * 
	 * @return <b>true</b> if currently connected to a host, <b>false</b>
	 *         otherwise.
	 */

	@Override
	public boolean isConnected() {

		if (socketClientServer.isConnected()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Disconnect from a Moodish server. Any sockets should be closed
	 * immediately. Pending messages are not guaranteed to be delivered before
	 * disconnecting.
	 */
	@Override
	public void disconnect() {
		// It's only possible disconnection

		if (isConnected()) {
			try {
				ClientSideMessageComponent message = new ClientSideMessageComponent(
						null, null, null);
				listOfMessages.put(message);
				socketClientServer.close();
				socketClientServer = null;

			} catch (IOException e) {
				System.out.println("ERROR - Closing Socket !!!");
			} catch (InterruptedException e) // thread is waiting, sleeping, or
												// otherwise occupied
			{
				System.out.println("ERROR - Message list !!!");
			}
		}

		else {
			System.out.println("You are Disconnected !!!");
		}

	}

	/**
	 * Get the next message received from the server. If no message is has been
	 * received, the method blocks until a message has been received. If no
	 * message will ever be received (connection is down), null is returned.
	 * 
	 * @return The next message sent by the server to the client or null if no
	 *         message will ever be received (disconnected from server).
	 */
	@Override
	public ClientSideMessage getNextMessage() {

		// Next message is only possible if the user is connected and there are
		// messages in the message list
		// There is a possibility that the list of messages is not empty but the
		// system cannot access it

		ClientSideMessage message = null;
		if (isConnected() && listOfMessages != null) {
			try {
				message = listOfMessages.take();

			} catch (InterruptedException e) // thread is waiting, sleeping, or
												// otherwise occupied
			{
				System.out
						.println("ERROR acessing the message list. Verify your conection !!!");
			}

		} else {
			System.out.println("You are Disconnected !!!");
		}
		return message;

	}

	/**
	 * Check if a message from the server is pending. If
	 * {@link #hasNextMessage()} returns <b>true</b>, a call to
	 * {@link #getNextMessage()} will return immediately with the oldest message
	 * in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call
	 * to {@link #getNextMessage()} will block until a message has been
	 * received.
	 * 
	 * @return <b>true</b> if a message from the server is currently pending,
	 *         otherwise <b>false</b>.
	 */
	@Override
	public boolean hasNextMessage() {

		return listOfMessages != null && !listOfMessages.isEmpty();

	}

	/**
	 * Send a Moodish message from this client to the server which should then
	 * relay the message to all freinds.
	 * 
	 * @param moodishMessage
	 *            The Moodish message to be sent.
	 */
	@Override
	public void sendMoodishMessage(String moodishMessage) {
		// The message must only be sent if a user is connected

		if (isConnected()) {
			ServerSideMessageComponent message = new ServerSideMessageComponent(
					nickname, moodishMessage,
					ServerSideMessage.Type.MOODISH_MESSAGE);
			try {
				outStream.writeObject(message);
			} catch (IOException e) {
				System.out.println("Error communicating with Server !!!");
			}
		} else {
			System.out.println("You are Disconnected !!!");
		}

	}

	/**
	 * Request to be friend of another client.
	 * 
	 * @param nickname
	 *            the nickname of the client that should be friend.
	 */
	@Override
	public void friendship(String nickname) {
		if (isConnected()) {
			ServerSideMessageComponent friendship = new ServerSideMessageComponent(
					nickname, null, ServerSideMessage.Type.FRIENDSHIP);

			try {
				outStream.writeObject(friendship);
			} catch (IOException e) {
				System.out.println("Error communicating with Server !!!");
			}
		} else {
			System.out.println("You are Disconnected !!!");
		}

	}

	/**
	 * Request to unfriend of a client.
	 * 
	 * @param nickname
	 *            the nickname of the client currently friend that should be
	 *            unfriended.
	 */
	@Override
	public void unfriendship(String nickname) {
		if (isConnected()) {
			ServerSideMessageComponent unfriendship = new ServerSideMessageComponent(
					nickname, null, ServerSideMessage.Type.UNFRIENDSHIP);

			try {
				outStream.writeObject(unfriendship);
			} catch (IOException e) {
				System.out.println("Error communicating with Server !!!");
			}
		} else {
			System.out.println("You are Disconnected !!!");
		}

	}

	@Override
	public void run() {
		try {
			while (isConnected()) {
				try {
					if (hasNextMessage()) {
						listOfMessages.put((ClientSideMessage) inStream
								.readObject());
					}
				} catch (InterruptedException e) {
					System.out
							.println("ERROR acessing the message list. Verify your conection !!!");
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			System.out
					.println("An error has occurred. Connection Terminated !!!");
		}

	}

}
